/* @flow */

import VNode from './vnode'
import { resolveConstructorOptions } from 'core/instance/init'
import { queueActivatedComponent } from 'core/observer/scheduler'
import { createFunctionalComponent } from './create-functional-component'

import {
  warn,
  isDef,
  isUndef,
  isTrue,
  isObject
} from '../util/index'

import {
  resolveAsyncComponent,
  createAsyncPlaceholder,
  extractPropsFromVNodeData
} from './helpers/index'

import {
  callHook,
  activeInstance,
  updateChildComponent,
  activateChildComponent,
  deactivateChildComponent
} from '../instance/lifecycle'

// hooks to be invoked on component VNodes during patch
// 只有组件的vnode对象才会添加hook，在patch的相应过程中，
// 先调用这些钩子方法对组件的vnode对象进行处理。
const componentVNodeHooks = {
  init (
    vnode: VNodeWithData,
    hydrating: boolean,
    parentElm: ?Node,
    refElm: ?Node
  ): ?boolean {
    //如果vnode.componentInstance对应的vue实例存在，说明该vnode对象是通过keep-alive
    //组件处理过的，这个vue实例是缓存在keep-alive组件中的，所以不需要重新创建vue实例，
    //而是只需简单的prepatch
    if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) {
      //根据虚拟dom对象中的信息创建相应的Vue实例
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        //不可以使用vnode的context对象，因为对于slot的替换组件来说，其context不是当前vue实例，
        //而是父vue实例
        activeInstance,
        // Todo parentElm和refElm各指向那个元素？主要是相对于当前待初始化组件之间的关系
        parentElm,
        refElm
      )
      //挂载新创建的Vue实例，在挂载过程中，会根据当前Vue组件的虚拟dom对象创建相应的元素。
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    } else if (vnode.data.keepAlive) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    }
  },

  //根据新的虚拟dom对象更新Vue实例中的相关属性，比如$listener、props、$vnode等
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },

  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    //为什么此处还需要调用mounted钩子方法？什么情况下componentInstance实例没有挂载？
    //答：因为对于子组件来说，其mounted不会在mountComponent方法中调用，详情可查看mountComponent方法
    //中的源码和注释。
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        // 也就是说，在patch过程中，虽然当前vue实例的inert hook已经调用，但是
        // 其子实例可能仍会改变，所以此处不可直接激活子组件。
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },

  //在patch过程中，根据vnode销毁vue实例或者将激活状态转换为未激活状态
  destroy (vnode: MountedComponentVNode) {
    //vnode对应的vue实例对象
    const { componentInstance } = vnode
    if (!componentInstance._isDestroyed) {
      //data属性上的keepAlive值是在何处设置的？
      //答：是在keep-alive组件上设置的，表示该vnode对象是被keep-alive处理的对象
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy()
      } else { //如果是被keep-alive标签包裹的元素，则不是直接销毁该元素，而是标记为未激活状态
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  }
}

const hooksToMerge = Object.keys(componentVNodeHooks)

//只在core/vdom/create-element.js中使用
//该方法用于根据参数创建Vue组件的虚拟dom对象
export function createComponent (
  Ctor: Class<Component> | Function | Object | void, //注意Ctor可能的类型
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | void {
  if (isUndef(Ctor)) {
    return
  }

  //_base指向Vue构造函数
  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  //支持调用createElement的第一个参数为选项对象
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component
  let asyncFactory
  //如果Ctor.cid为undefined，则说明Ctor是异步组件，因为如果是普通的组件，则会通过
  //Vue.extend方法处理，从而组件函数会存在cid属性。
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)

    //如果为undefined，则说明在异步解析组件，所以直接返回一个占位vnode对象
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }

  data = data || {}

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)

  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // functional component
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // keep listeners
  const listeners = data.on

  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot
    //如果是抽象组件，比如keep-alive，则除了props、listeners和slot，其他的
    //属性都不需要，直接删除掉。
    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // merge component management hooks onto the placeholder node
  //只有Vue组件才会在虚拟dom中添加相应的钩子函数，这些钩子方法会在虚拟dom使用的不同
  //阶段被调用，比如init钩子方法用于根据组件的构造函数信息创建相应的Vue实例。详情可查看
  //componentVNodeHooks变量中的钩子方法的定义
  mergeHooks(data)

  // return a placeholder vnode
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },//这里是设置componentOptions，组件相关的选项
    asyncFactory
  )
  return vnode
}

export function createComponentInstanceForVnode (
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any, // activeInstance in lifecycle state
  parentElm?: ?Node,
  refElm?: ?Node
): Component {
  //componentOptions属性是在上面的createComponent方法中定义的，是在
  //创建Vue组件的虚拟dom对象时设置的。
  const vnodeComponentOptions = vnode.componentOptions
  const options: InternalComponentOptions = {
    _isComponent: true,
    parent,
    propsData: vnodeComponentOptions.propsData, //父组件向子组件传入的props属性对应的值
    _componentTag: vnodeComponentOptions.tag, //该属性主要用于生成错误信息
    _parentVnode: vnode,
    _parentListeners: vnodeComponentOptions.listeners, //组件特定的事件监听，需要通过$emit触发。
    //对于组件来说，组件标签内部的元素需要传入组件的Vue实例内，而不是出现在当前组件的vnode的children数组中
    _renderChildren: vnodeComponentOptions.children,
    _parentElm: parentElm || null,
    _refElm: refElm || null
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  //使用inline-template作为模板
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  return new vnodeComponentOptions.Ctor(options)
}

function mergeHooks (data: VNodeData) {
  if (!data.hook) {
    data.hook = {}
  }
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    //可能从父组件也会传入hook函数
    //@todo 什么hook会从父组件中传过来？
    const fromParent = data.hook[key]
    const ours = componentVNodeHooks[key]
    data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours
  }
}

function mergeHook (one: Function, two: Function): Function {
  return function (a, b, c, d) {
    one(a, b, c, d)
    two(a, b, c, d)
  }
}

// transform component v-model info (value and callback) into
// prop and event handler respectively.
// 在组件中使用v-model指令，只不过是对于v-bind和v-on两个指令的语法糖，
// 其中v-bind的参数（v-bind:value）为options.model.prop指定或者'value'，
// 对于v-on的参数(v-on:input)为options.model.event指定或者'input'
function transformModel (options, data: any) {
  //options.model用于定制组件的v-model，详情可见文档：教程-->组件-->定制组件的v-model
  //在这里处理v-model的原因是需要获得构造函数options中的可能存在的model属性的值
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'
  ;(data.props || (data.props = {}))[prop] = data.model.value
  const on = data.on || (data.on = {})
  if (isDef(on[event])) {
    //v-model的回调函数最先执行
    on[event] = [data.model.callback].concat(on[event])
  } else {
    on[event] = data.model.callback
  }
}
